/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufrn.engcomp.pds.helloopc.modelo.negocio;

import br.ufrn.engcomp.pds.helloopc.algoritmos.*;
import br.ufrn.engcomp.pds.helloopc.modelo.dominio.Controlador;
import br.ufrn.engcomp.pds.helloopc.modelo.dominio.ParametroConfiguracao;
import br.ufrn.engcomp.pds.helloopc.modelo.dominio.ServidorOPC;
import br.ufrn.engcomp.pds.helloopc.modelo.dominio.TipoVariavelOPC;
import br.ufrn.engcomp.pds.helloopc.modelo.dominio.VariavelControlador;
import br.ufrn.engcomp.pds.helloopc.modelo.negocio.excecao.NegocioException;
import br.ufrn.engcomp.pds.helloopc.modelo.negocio.opc.ConexaoOPC;
import br.ufrn.engcomp.pds.helloopc.modelo.negocio.servico.ControladorServico;
import br.ufrn.engcomp.pds.helloopc.modelo.negocio.servico.ServidorOPCServico;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Felipe Rivas
 */
public class ExecutarControladorNegocio extends AbstractNegocio {

    private ControladorServico controladorServico = new ControladorServico();
    private ServidorOPCServico servidorOPCServico = new ServidorOPCServico();
    private Thread threadExecucao = null;
    private boolean threadEmManual = false;
    private Map<String, Object> valoresEntrada;
    private Map<String, Object> valoresSaida;

    public Map<String, Object> getValoresEntrada() {
        return valoresEntrada;
    }

    public Map<String, Object> getValoresSaida() {
        return valoresSaida;
    }

    public void setValoresEntrada(Map<String, Object> valoresEntrada) {
        this.valoresEntrada = valoresEntrada;
    }

    public void setValoresSaida(Map<String, Object> valoresSaida) {
        this.valoresSaida = valoresSaida;
    }

    public void executar(final Controlador controlador) {
        threadExecucao = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    final String CAMINHO = "file:arquivos/" + controlador.getId() + ".class";

                    List<VariavelControlador> variaveisProcesso = controlador.getVariaveisTipo(TipoVariavelOPC.PROCESSO);
                    List<VariavelControlador> variaveisManipuladas = controlador.getVariaveisTipo(TipoVariavelOPC.MANIPULADA);
                    VariavelControlador variavelSetPoint = controlador.getVariaveisTipo(TipoVariavelOPC.SETPOINT).get(0);
                    VariavelControlador variavelWatchdog = controlador.getVariaveisTipo(TipoVariavelOPC.WATCHDOG).get(0);

                    Map<String, Object> valoresProcesso = new HashMap<String, Object>();
                    Map<String, Object> valoresProcessoLido = new HashMap<String, Object>();
                    Map<String, Object> valoresManipuladas = null;
                    Object valorSetPoint = 0;
                    double valorSetPointReal = 0;
                    Object valorWatchdog = 0;
                    double valorWatchdogReal = getValorReal(valorWatchdog, variavelWatchdog);

                    int i = 1;
                    long periodoAmostragem = (long) (controlador.getPeriodoAmostragem() * 1000);
                    long tempoAtual = 0;
                    long tempoInicio = 0;
                    long tempoFim = 0;
                    long tempoRestante = 0;

                    URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL(CAMINHO)});
                    Class classe = classLoader.loadClass(controlador.getClasseAlgoritmo());
                    IAlgoritmoControlador algoritmo = (IAlgoritmoControlador) classe.newInstance();

                    for (ParametroConfiguracao pc : controlador.getParametrosConfiguracao()) {
                        algoritmo.setParametro(pc.getNome(), pc.getValor());
                    }

                    ConexaoOPC.getCurrentInstance().setVariavel(variavelWatchdog.getTagOPC().getNome(), valorWatchdogReal);

                    while (true) {
                        /*try {
                        synchronized(ExecutarControladorNegocio.this) {
                        while (threadEmManual) {
                        ExecutarControladorNegocio.this.wait();
                        }
                        }
                        } catch (InterruptedException iex) {
                        iex.printStackTrace();
                        //TODO: Tratar erro
                        }*/

                        tempoInicio = System.currentTimeMillis();

                        for (VariavelControlador v : variaveisProcesso) {
                            Object valorLido = ConexaoOPC.getCurrentInstance().getVariavel(v.getTagOPC().getNome());
                            double valorLidoReal = getValorReal(valorLido, v);

                            valoresProcessoLido.put(v.getNome(), valorLido);
                            valoresProcesso.put(v.getNome(), valorLidoReal);
                            System.out.println("Entrada: " + valorLido);
                        }

                        valorSetPoint = ConexaoOPC.getCurrentInstance().getVariavel(variavelSetPoint.getTagOPC().getNome());
                        valorSetPointReal = getValorReal(valorSetPoint, variavelSetPoint);
                        System.out.println("Referência: " + valorSetPointReal);

                        tempoAtual = i++ * periodoAmostragem;
                        System.out.println("Tempo: " + tempoAtual);

                        if (!threadEmManual) {
                            valoresManipuladas = algoritmo.calcularResposta(valoresProcesso, valorSetPointReal, tempoAtual);
                        } else {
                            valoresManipuladas = valoresSaida;
                        }

                        for (VariavelControlador v : variaveisManipuladas) {
                            Object valorCalculado = valoresManipuladas.get(v.getNome());
                            double valorCalculadoReal = getValorReal(valorCalculado, v);

                            ConexaoOPC.getCurrentInstance().setVariavel(v.getTagOPC().getNome(), valorCalculadoReal);
                            System.out.println("Saída: " + valorCalculado);
                        }

                        valoresEntrada = valoresProcessoLido;
                        valoresSaida = valoresManipuladas;

                        tempoFim = System.currentTimeMillis();

                        tempoRestante = periodoAmostragem - (tempoFim - tempoInicio);

                        if (tempoRestante < 0) {
                            throw new NegocioException("Deu a louca na chapeuzinho.");
                        }

                        Thread.sleep(tempoRestante);
                    }
                } catch (InterruptedException iex) {
                    iex.printStackTrace();
                    //TODO: Tratar erro
                } catch (MalformedURLException mfuex) {
                    mfuex.printStackTrace();
                    //TODO: Tratar erro
                } catch (ClassNotFoundException cnfex) {
                    cnfex.printStackTrace();
                    //TODO: Tratar erro
                } catch (InstantiationException iex) {
                    iex.printStackTrace();
                    //TODO: Tratar erro
                } catch (IllegalAccessException iaex) {
                    iaex.printStackTrace();
                    //TODO: Tratar erro
                } catch (NegocioException nex) {
                    nex.printStackTrace();
                    //TODO: Tratar erro
                } catch (Exception ex) {
                    ex.printStackTrace();
                    //TODO: Tratar erro
                }
            }

            private double getValorReal(Object valorPrevio, VariavelControlador variavelControlador) throws Exception {
                double valorReal = Double.parseDouble(valorPrevio.toString());

                if (variavelControlador.getTagOPC().getTipo() == TipoVariavelOPC.MANIPULADA ||
                        variavelControlador.getTagOPC().getTipo() == TipoVariavelOPC.WATCHDOG) {
                    if (valorReal < variavelControlador.getLimiteMinimo()) {
                        return variavelControlador.getLimiteMinimo();
                    } else if (valorReal > variavelControlador.getLimiteMaximo()) {
                        return variavelControlador.getLimiteMaximo();
                    } else {
                        return valorReal;
                    }
                } else if (variavelControlador.getTagOPC().getTipo() == TipoVariavelOPC.PROCESSO ||
                        variavelControlador.getTagOPC().getTipo() == TipoVariavelOPC.SETPOINT) {
                    return valorReal * variavelControlador.getEscalaConversao();
                } else {
                    throw new Exception("Tipo de tag OPC inválida.");
                }
            }
        });

        threadExecucao.start();
    }

    public void parar() {
        if (emExecucao()) {
            threadExecucao.interrupt();
        }
    }

    public boolean emExecucao() {
        return threadExecucao != null && threadExecucao.isAlive();
    }

    public List<Controlador> listarControladores() throws NegocioException {
        if (ConexaoOPC.isConnected()) {
            ServidorOPC servidorOPCConectado = servidorOPCServico.buscarPorID(ConexaoOPC.getCurrentInstance().getId());

            return servidorOPCConectado.getControladores();
        }

        throw new NegocioException("Não está conectado a um servidor OPC.");
    }

    public List<Controlador> sincronizarControladores(ServidorOPC entidade) {
        controladorServico.refresh(entidade.getControladores());

        return entidade.getControladores();
    }

    public synchronized void colocarEmModoAutomatico() {
        threadEmManual = false;

        //ExecutarControladorNegocio.this.notify();
    }

    public synchronized void colocarEmModoManual() {
        threadEmManual = true;
    }
}
